<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>ECMAScript 6 code</title>
</head>
<body>
  <script>
        let log = console.log
        let error = console.error
        let total = 30;
        let msg = passthru`The total is ${total} (${total*1.05} with tax)`;

        function passthru(literals) {
          let result = '';
          let i = 0;
          
          while (i < literals.length) {
            result += literals[i++];
            if (i < arguments.length) {
              result += arguments[i];
            }
          }

          return result;
        }
        function passthruRest(literals, ...values) {
          let output = "";
          let index;
          for (index = 0; index < values.length; index++) {
            output += literals[index] + values[index];
          }

          output += literals[index]
          return output;
        }

        let template = `
        <ul>
          <% for(let i=0; i < data.supplies.length; i++) { %>
            <li><%= data.supplies[i] %></li>
          <% } %>
        </ul>
        `;

       /*
        *ehco('<ul>')
        *for(let i=0; i < data.supplies.length; i++) {
        *ehco('<li>')
        *ehco(data.supplies[i])
        *echo('</li>')
        *}
        *ehco('</ul>')
        */
        function compile(template){
          const evalExpr = /<%=(.+?)%>/g
          const expr = /<%([\s\S]+?)%>/g

          template = template.replace(evalExpr, "`) \n echo( $1 ) \n echo(`")
                             .replace(expr, "`) \n $1 \n echo(`")

          template = "echo(`" + template + "`)"

          let js = `(function parse(data){ 
                      let htmlContent = "" 
                      function echo(v){ 
                       htmlContent += v  
                      }
                      ${ template }
                      return htmlContent
                   })`;
          return js;
        }
        function compile2(template){
          const evalExpr = /<%=(.+?)%>/g;
          const expr = /<%([\s\S]+?)%>/g;

          template = template
            .replace(evalExpr, '`); \n  echo( $1 ); \n  echo(`')
            .replace(expr, '`); \n $1 \n  echo(`');

          template = 'echo(`' + template + '`);';

          let script =
          `(function parse(data){
            let output = "";

            function echo(html){
              output += html;
            }

            ${ template }

            return output;
          })`;

          return script;
        }
        var parse = eval(compile(template))
        var parse1 = eval(compile2(template))
        var data = {supplies:['1','2','3']}

        function scope() {
          let str = '(nickName) => `Hello ${this} ${nickName}!`';
          let func = eval.call(null, str);
          let func1 = eval(str);
          console.log(func())
          console.log(func1())
        }


        const pipeline = function (...funcs) {
          return function (val) {
            return funcs.reduce(function(a,b){
              return b(a)
            },val)
          }
        }
        const plus1 = function (a){
          return a+1
        }
        const mults = function (a){
          return a * 2
        }

        const addThenMult = pipeline(plus1,mults)


        function tco(f) {
          var value;
          var active = false;
          var accumulated = [];

          return function accumulator() {
            arguments[0] && accumulated.push(arguments);
            if (!active) {
              active = true;
              while (accumulated.length) {
                value = f.apply(this, accumulated.shift());
              }
              active = false;
              return value;
            }
          };
        }

        var sum = tco(function(x, y) {
          if (y > 0) {
            return sum(x + 1, y - 1)
          }
          else {
            return x
          }
        });

       // console.log(sum(1, 100000))

       var pipe =  function (value) {
            var funcs = []
            var proxy = new Proxy({},{
              get(target,prop) {
                if (prop!=='get') {
                  funcs.push(window[prop])
                  return proxy
                } else {
                  return funcs.reduce(function(val,fn){
                    return fn(val)
                  },value)
                }
              }
            })

            return proxy
          }

        const dom = new Proxy({}, {
          get (target, property) {
            return function (attr = {}, ...children) {
              let e = document.createElement(property)
              for (let prop in attr) {
                e.setAttribute(prop, attr[prop])
              }
              children.forEach(v=>{
                if(typeof v === 'string') {
                  v = document.createTextNode(v)
                }
                e.appendChild(v)
              })

              return e
            }
          }
        })

        const el = dom.div({},
            'Hello , my name is ',
            dom.a({href: '//example.com'}, 'mark'),
            ', I like:',
            dom.ul({},
                dom.li({}, 'The web'),
                dom.li({}, 'Food'),
                dom.li({}, '…actually that\'s it')
              )
          )

        const handler = {
          get (target, key) {
            invariant(key, 'get');
            return target[key];
          },
          set (target, key, value) {
            invariant(key, 'set');
            target[key] = value;
            return value
          }
        }
        function invariant (key, action) {
          if (key[0] === '_' ) {
            throw new Error(`Invalid attempt to ${action} private "${key}"  property`);
          }
        }
        const target = {}
        const iproxy = new Proxy(target, handler);

        var twice = {
          apply (target, ctx, args) {
            return Reflect.apply(...arguments) * 2
          }
        }
        function sum (left, right) {
          return left +right;
        }
        var aproxy = new Proxy(sum, twice)

        function textContent(e) {
          let child, type, s = "";
          for (child=e.firstChild;child!=null;child = child.nextSibling) {
            type = child.nodeType
            if (type===3||type===4)
              s += child.nodeValue;
            else if (type===1) 
              s += textContent(child)
          }
          return s;
        }

        function textContent2(e, child, s){
          if(child === undefined && s === undefined) 
            return textContent2(e, e.firstChild, "")
          let type;
          if (child === null) {
            if (e._parent) {
              return textContent2(e._parent, e.nextSibling, s)
            } else {
              return s
            }
          }
          type = child.nodeType
          if (type===3||type===4)
            return textContent2(e, child.nextSibling, s+child.nodeValue)
          else if (type===1) {
            child._parent = e
            return textContent2(child, child.firstChild, s)
          }
        }
        //console.time("textContent：");
        //textContent(div);
       // console.timeEnd("textContent：");
        //console.time("textContent2：");
       // textContent2(div);
       // console.timeEnd("textContent2：");

/*
       log(1);
       setTimeout(function(){
         log(2);
       });
       log(3);
       let p = new Promise(function(resolve, reject){
        log(4)
        resolve()
       })
       log(5)
       p.then(function(){
        log(6)
       })
       log(7)*/
    function timeout(ms) {
      return new Promise((resolve, reject) => {
        setTimeout(resolve, ms, 'done');
      })
    }
    //timeout(100).then(value => {console.log(value);})
    //console.log(1)
/*
    let promise = new Promise((resolve, reject) => {
      console.log('Promise');
      resolve && resolve();
    })*/
    //promise.then(() => {console.log('resolved.')},() => console.log('Err'))
    //console.log("Hi!")
    function loadImage(url,callback){
        const img = new Image();
        img.onload = () => {
          callback(1, img)
        }

        img.onerror = () => {
          callback(0, new Error(`Could not load img at ${url}`) );
        }

        img.src = url ;
    }
    function loadImageAsync(url) {

      return new Promise( (resolve, reject) => {
        const img = new Image();
        img.onload = () => {
          resolve(img)
        }

        img.onerror = () => {
          reject( new Error(`Could not load img at ${url}`) );
        }

        img.src = url ;
      })
      
    }

    function loadImageAsyncCatch(url) {
      return loadImageAsync(url).catch(err=>log(err,'from Promise'))
    }


    const getJSON = function (url)　{

      return new Promise( (resolve, rejected) => {
        let handler = function () {
          if ( this.readyState !==4 )
            return ;
          if ( this.status === 200 ) {
            resolve(this.response)
          } else {
            rejected(new Error(this.statusText))
          }
        }
        let client = new XMLHttpRequest()
        client.open('GET', url)
        client.onreadystatechange = handler
        client.responseType = 'json'
        client.setRequestHeader('Accept', 'application/json')
        client.send(null)

      })

    }


/*    let promise1 = new Promise((resolve, reject) => {
      setTimeout(() => resolve('132131'), 3000)
    })
    let promise2 = new Promise((resolve, reject) => {
      setTimeout(resolve, 1000, promise1)
    }).then({a:1}, {a:1}).then((v)=>{console.log(v)})*/
/*    promise2.then(v=>{console.log(v)},v=>{
      console.log(v)
    })*/
/*    const p1 = new Promise(function (resolve, rejected) {
      setTimeout(() => rejected(new Error('fial')), 3000)
    })

    const p2 = new Promise(function (resolve, rejected) {
      setTimeout(() => resolve(p1), 1000)
    })

    p2
      .then(result => console.log(result))
      .catch(error => console.error(error))*/
/*      setTimeout(() => console.log(1),10)
      new Promise((resolve, reject) => {
        setTimeout(()=>{resolve(2)},10)
        console.log(3)
      }).then(r => {
        console.log(r)
      })*/

/*      new Promise((resolve, reject) => {        
        console.log(2);
        return resolve(1);
        console.log(2);
      })*/
/*      new Promise((res, rej) => {
        return res(1)
      }).then(v => console.log(v))*/

      /*
      Promise.prototype.then()
      Promise实例的then()方法，作用是为Promise实例添加状态改变时的回调。第一个参数是resolved状态的回调，第二个参数是rejected状态的回调。可省略或使用非函数变量作为实参，不会报错，等同末设置状态改变的回调，无意义。
      
      then方法的链式调用
      then方法返回的是一个新的Promise实例,因此可以采用链式写法,即then方法之后再调用另一个then方法。前一个then方法的回调函数的返回值会做为参数传入下一个then方法的回调函数。如果前一个then方法的回调函数返回一个Promise对象，这时下一个then会等待Promise对象状态发生变化，才会被执行。
      */
/*      new Promise((res,rej) => {
        res(1)
      }).then(v=>{console.log(d)}).catch(err => {console.error(err)})*/
/*      new Promise((res, rej) => {
        console.log(b)
        res(1)
      }).then(()=>{},err => {})*/

/*      const promise = new Promise((res,rej) => {
        setTimeout(res);
        throw new Error('test')
      })
      promise
        .then(v=>{console.log(v)})
        .catch(err=>{error(err)})*/

/*        const someAsyncThing = () => new Promise((res, rej) => res(x + 2))
        someAsyncThing().then(()=>{
          log('everthing is great');
        })
        setTimeout(() => {log('123123')}, 2000)

        const promise = new Promise((res, rej) => {
          throw new Error('tedt')
          res('ok')
        })
        promise.then(v => {console.log(v)})
       setTimeout(() => {log('123123')}, 2000)
       
       Promise.prototype.catch()
       Promise.catch()是.then(null, rejction)的别名，用于指定Prmois对象发生错误时的回调函数，还可以捕获之前的then方法中回调函数的错误。catch方法同样也返回一个Promise对象。
       Promise对象的错误具有“冒泡”性质，会一直向后传送，直至被捕获(可以是catch或then方法的rejected回调)，因为catch还能捕获前面then方法执行中的错误，因此那座总是使用catch方法，而不使用then方法的第二个参数。


    Promise.prototype.finally = function (callback) {
      let P = this.constructor;
      return this.then(
        value => P.resolve(callback()).then(() => value),
        reason => P.resolve(callback()).then(() => {console.log(reason);return reason;} )
      );
      new Promise((res, rej)=>{
        rej('1')
      }).finally((v)=>{console.log(v)}).then(v=> log(v), err => log(err))
    }*/
/*    window.onerror = function(err){
      debugger;
    }

    Promise.prototype.finally()
    finally方法用于指定不管Promise对象最后状态如何，都会执行操作。该方法是ES2018标准引入的。finally方法的回调不接受任何参数，finally方法里的操作与状态无关，不依赖Promise的执行结果。

    log(c)

    const promises = ['1', '2', '3', '4'].map(function (name) {
      return loadImageAsync(`./images/${name}.jpg`)
    })

    Promise.all(promises).then(function (posts) {
      log(posts)
    }).catch((err) => {
      log(err)
    })
    

   const promises2 = ['1', '2', '3', '4'].map(function (name) {
      return loadImageAsyncCatch(`./images/${name}.jpg`)
    })

    Promise.all(promises2).then(function (posts) {
      log(posts)
    }).catch((err) => {
      log(err,'from promises')
    })

    Promise.all()
    Promise.all方法用于将多个Promise实例，包装成一个新Promise实例。Promise.all方法接受一个数组作为参数，如果数组元素不是Promise实例，将会调用Promise.resolve方法将元素转换为Promise实例。只有数组所有Promise实例状态都变成fulfilled,才会调用新Promise的fulfilled回调，参数为所有Promise实例返回值组成的数组。只要数组中有一个Promise实例的状态变成rejected,此时每一个被reject的实例的返回值，会传递给新Promise实例的rejected的回调函数。
    如果作为参数的Promise实例自己有定义catch方法，那么它一旦被rejected，并不会触发Promise.all()的catch方法。

    */

 /*   const promisesRace = ['1', '2', '3', '4'].map(name => loadImageAsyncCatch(`./images/${name}.jpg`))
    Promise.race(promisesRace)
      .then(log)
      .catch(err=>{
        log(err,'from promiseRace')
      })
    Promise.race()
    Promise.race方法同样是将多个Promise实例，包装成一个新的Promise实例。接受一个Promise实例数组作为参数，如果数组元素不是Promise使用，将使用Promise.resolve方法转为Promise实例。
    只要Promise数组中有一个Promise实例状态率先发生改变，新Promise实例的状态也会改变。这个率先改变的Promise的实例的返回值，会传递给新包装的Promise实例的回调函数。
    如果作为参数的自己有定义catch方法，那么它一旦被rejected，并不会触发Promise.race()的catch方法。
     
      var a = {n:1}
      var b = a ;
      a.x = a = {n:2}
      log(a.x)
      log(b.x) 
      let thenable = {
        then (res, rej) {
         // rej(42)
        }
      }
      let p1 = Promise.resolve(thenable);
      p1.then(log, error)

      const resolveString = Promise.resolve('Hello')
      resolveString.then(log)

      const resolveObj = Promise.resolve({a:1,b:2})
      resolveObj.then(log, error)
      
      setTimeout(function(){
        log('three');
      }, 0)
      Promise.resolve().then(() => log('two'))

      log('one')

      Promise.resolve()方法
      Promise.resolve方法的作用是变量转换为Promise对象。
      Promise.resolve方法的参数分为四种情况
      1.参数是一个Promise实例：不做任何修改，直接返回。
      2.参数是一个thenable对象(具有then方法的对象)：将这个对象转换为Promise对象，然后立即执行thenable对象的then方法。
      3.参数是不具有then方法的对象，或者根本不是对象：返回一个状态为resolved的新Promise对象，同时resolve方法的参数会传递给回调。
      4.不带任何参数：返回一个resolved状态的Promise对象。
      立即resolve的Promise对象是在本轮"事件循环(event loop)"结束时执行，而不是下轮"事件循环"的开始时(setTimeout)。


      
      let p1 = new Promise((res, rej) => {})
      Promise.reject(p1)   
      const thenable = {
        then (res, rej) {
          rej('出错了 from then ')
        },
        catch (err) {
           log(err+' from catch1')
        }
      }
      Promise.reject(thenable)
        .catch(err=>log(err))

      Promise.reject()
      Promise.reject方法返回一个状态为rejected的新Promise实例。Promise。reject()方法的参数，会原封不动地作为reject的理由，变成后续方法的参数。

      Promise.try方法(提案),让同步函数同步执行，异步函数异步执行，并提供统一的API。
    


       const f = new Promise((res, rej) => res(log));
       (
        () => new Promise(
           resolve => resolve(f)
          )
       )()
       log('next')  

      遍历器(Iterator)是一种接口，为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口，就可以完成遍历操作(即依次处理该数据结构的所有成员)。Iterator接口与它所遍历的数据结构是分开的。

      Iterator的作用有三个：一是为各种数据结构，提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是ES6创造了一种新的遍历命令for...of循环，Iterator接口主要是供for...of消费。


      Iterator的遍历过程：
      (1)、创建一个指针对象，指向当前数据结构的起始位置。也就是说，遍历器本质上，就是一个指针对象。
      (2)、第一次调用指针对象的next方法，可以将指针指向数据结构的第一个成员。
      (3)、第二次调用指针对象的next方法，指针就指向数据结构的第二个成员。
      (4)、不断调用指针对象的next方法，直到它指向数据结构的结束位置。
      每次调用都会返回一个包含value和done两个属性的对象。其中value属性是当前成员的值，done属性是一个表示遍历是否结束的布尔值。



      


      
              */
      function makeIterator(arr) {
        let nextIndex = 0 
        return {
          next () {
            return nextIndex < arr.length ?
              {value: arr[nextIndex++], done: false} :
              {value: undefined, done: true};
          }
        }
      }

      function makeInteratorE(arr) {
        let nextIndex = 0;
        let len = arr.length
        return {
          next () {
            return nextIndex < len ? {value: arr[nextIndex++]} : {done: true}
          }
        }
      }

      function idMake () {
        let index = 0 ;
        return {
          next () {
            return {value: index++, done: false}
          }
        }
      }

    // var it = makeIterator(['a', 'b'])

    /*
     


    */

    const iterableObj = {
      [Symbol.iterator] : function () {
        return {
          next () {
            return {
              value: 1,
              done: false
            }
          }
        }
      }
    }

    /*
     ES6规定，默认的Iterator接口部署在数据结构的Symbol.iterator属性，或者说，一个数据结构只要有Symbol.iterator属性，就可以认为是“可遍历的(iterable)”。凡是部署了Symbol.iterator 属性的数据结构，就称为部署了遍历器接口。调用这个接口，就会返回一个遍历器对象。原生具备Iterator接口的数据结构有：Array、Map、Set、String、TypedArray、函数的arguments对象、NodeList对象。
    给一个对象添加iterator接口，必须在Symbol.iterator的属性上部署遍历器生成方法(原型链上的对象具有该方法也可)。类数组对象让Symbol.iterator属性直接引用数组的iterator接口。Symbol.iterator方法如果不能返回一个遍历器对象，遍历时会报错。
        */

    class RangeIterator {
      constructor (start, stop) {
        this.value = start
        this.stop = stop
      }
      [Symbol.iterator] () { return this }
      next () {
        return this.value < this.stop ? {value: this.value++} : {done: true} 
      }
    }

    function range(start, stop) {
      return new RangeIterator(start, stop)
    }

    function Obj (value) {
      this.value = value
    }

    Obj.prototype[Symbol.iterator] = function () {
      let current = this
      return {
        next () {
          if (current) {
            let val = current.value
            current = current.next
            return {value: val}
          } else {
            return {done: true}
          }
        }
      }
    }

    function ObjRange(start, stop) {
      let first = new Obj(start++);
      let nextObj = new Obj(start++);
      let obj ;
      if (start <= stop) 
        first.next = nextObj;
      else 
        return first;

      for( ; start <= stop; start++){
        obj = new Obj(start)
        nextObj.next = obj
        nextObj = obj
      }
      return first
    }

/*    let objArray = {
      data: ['hello', 'world'],
      [Symbol.iterator] () {
        const self = this;
        let index = 0;
        return {
          next () {
            return index < self.data.length ? {value: self.data[index++]} : {done: true}
          }
        }
      }
    }*/

   

   

    let generator = function* () {
      yield 1;
      yield [2, 3, 4];
      yield* ObjRange(5,10);
      yield 5;
    }

    //var iterator = generator();
 /*
    默认调用Iterator接口的场合:
    1、解构赋值
    2、扩展运算符
    3、yield* 后面跟一个可遍历的结构
    4、其它场合：由于数组遍历会调用遍历器接口，所以任何接受数组作为参数的场合，其实都调用了遍历接口。如:for...of、Array.from()、Map()、Set()、WeakMap()、WeakSet()、Promise.all()、Promise.race()


    

    let str = new String("hi");

     log([...str])

     String.prototype[Symbol.iterator] = function() {
      return {
        next () {
          if (this._first) {
            this._first = false
            return {value: 'bye', done: false}
          } else {
            return {done: true}
          }
        },
        _first: true
      }
     }
  字符串的Iterator接口
  字符串原生具有iterator接口。原生遍历器会类数组方式遍历字符串的字符(能够识别四字节字符)
  Iterator接口最简单实现方式是使用Generator函数

  遍历器对象的return()、throw()
  遍历器对象除了具有next方法，还可以具有return和throw方法(自己布置遍历哭哭器生成对象时，可选)。
  return方法在提前退出for...of循环时(报错、break、continue);
  throw方法主要是配合 Generator 函数使用;

  for...of循环
  ES6引入了for...of循环，作为遍历所有数据结构的统一的方法。
  for...of内部调用的是数据结构的Symbol.iterator方法。一个数据结构只要部署了Symbol.iterator属性，就被视为具有iterator接口，就可以用for..of循环遍历它的成员。
  for...of循环可以使用的范围包括数组、Set和Map结构、某些类数组对象(arguments、DOM NodeList对象)、字符串、Generator对象。

  for...of和for...in遍历数组的不同
  for...of循环调用遍历器接口，数组的遍历器接口只返回具有arr数字索引的属性值。for...in循环只能获得键名，不会遍历空数组元素。for...of遍历如果想获取数组索引，可以借助数组实例的entries方法。

  Set和Map结构使用for...of循环
  Set和Map结构原生具有iterator接口，可以直接使用for...of循环。Set结构遍历时，返回一个值;Map结构遍历时，返回当前Map成员[的键名和键值组成的数组;

  计算生成的数据结构
  有些数据是在现有数据结构的基础上，计算生成的。比如，ES6的数组、Set、Map都部署了entries()、keys()、values()方法，调用后返回遍历器对象。
  entries()返回一个遍历器对象，用来遍历[键名,键值]组成的数组。对于数组，键名就是索引;对于Set，键名与键值相同;Map结构的Iterator接口接口，默认就是调用entries方法。
  keys()返回一个遍历器对象，用来遍历所有键名。
  values()返回一个遍历器对象，用来遍所有键值。

  类数组对象使用for...of循环
  arguments、NodeList、字符串可以直接使用for...of循环遍历;不具有Iterator接口的类数组对象可以使用Array.from方法将其转为数组，再使用for...of遍历。

  for...of遍历对象
  for...of不能直接遍历普通对象，除非已经Iterator接口。一种解决方案是，使用Objece.keys()方法将对象的键名生成一个数组，然后遍历这个数组。另一种方法是使用Generator函数对象重新包装一下。

  for...of与其它遍历方法的比较
  for循环比较麻烦;forEach不能中途跳出;for...in会遍历其他键，甚至包括原型链上的键，并且以下字符串作为键名，某些情况下循环会心任意顺序遍历。
  for...of有着for...in简洁的语法，但没有for...in的缺点;
  不同于forEach,for..of可以与break、continue、return配合使用;
  for...of提供了遍历所有数组的统一操作接口


      Generator函数是ES6提供的一种异步编程解决方案，语法行为与传统函数完全不同。
      从语法，Generator函数是一个状态机，封装了多个内部状。
      执行Generator函数会返回一个遍历器对象，就是说Generator函数还是一个遍历器生成函数。由于Gneerator函数返回的遍历器对象，只有调用next方法都会遍历下一个内部状态，所以其实是提供了一种可以暂停执行的函数。

      Generator和普通函数的异同
      形式上，Generator函数是一个普通函数，但是有两个特征。一是，function关键字与函数名之间有一个星号(ES6没有规定星号具体位置);二是，函数内部使用yield表达式，定义不同的内部状态。
      Generator函数的调用方法与普通函数一样。不同的是，Generator调用后并不执行，而是返回一个指向内部状态的指针对象(Iterator Object)。
      Generator函数是分段执行的，yield表达式是暂停执行的标记，而next方法可以恢复执行。

      yield表达式
      yield表达示就是Generator函数的暂停标志，只能在Generator中使用。yield表达式后面的表达式，只有当调用了next方法、内部指针指向该语句时才会执行，因此等于为JavaScript提供了手动“惰性求值(Lazy Evaluation)"的语法功能。Generator函数不使用yield表达式，就就成了一个单纯的暂缓执行函数。

      Generator返回的遍历器对象的next方法的运行逻辑。
      1、遇到yield表达式，就暂停执行后面的操作，并将紧跟在yield后面的那个表达式的值，作为返回的对象的value属性。
      2、下次调用next方法时，再继续往下执行，直到遇到下一个yield表达式。
      3、如果没有再遇到yield表达式，就一直运行到函数结束，直到return语句为止，并将return语句后面的表达式的值，作为返回的对象的value的属性值。
      4、如果该函数没有return语句，则返回的对象的value属性值为undefined。
      */

      function* helloWorldGenerator() {
        yield 'hello';
        console.log(1111);
        console.log(2222);
        yield 'world';
        return 'ending'
      }

   /*    var arr = [1,[[2,3],4],[5,6]];
      var flat = function* (a) {
        a.forEach(function* (item){
          if(typeof item !== 'number'){
            yield* flat[item]
          } else{
            yield item;
          }
        });
        return a;
      };
     for (let f of flat(arr).next().value){
        console.log(f)
      }*/

      function* yieldDemo() {
        log('hello0');
        log('Hello1' + (yield 123));
        log('Hello1' + (yield 456));
        log(1),log(2),log(3),(yield 5);
        if(1){
          yield true;
        } else {
          yield false;
        }
      }

      /*
      与Iterator接口的关系
      由于Generator函数就是遍历器生成函数，因此可以把Generator赋值给对象的Symbol.iterator属性，从而使得对象具有Iterator接口。
      Generator函数返回的遍历器对象本身也具有Symbol.iterator属性，执行后返回自身。
      
      next方法的参数
      yield表达式本身没有返回值，或者说问题返回undefined。next方法可以带一个参数，该参数就会被当作上一个yield表达式的返回值。这个功能有很重要的语法意义，可以在Generator函数运行的不同阶段，从外部向内注入不同的值，从而调整函数行为。

      */
      function* nextArg(){
        for(let i =0; true; i++){
          let reset = yield i;
          if (reset) { i = -1}
        }
      }

      function* foo(x) {
        let y = 2 * (yield (x +1));
        let z = yield (y / 3);
        return (x + y + z)
      }

      function* dataConsumer () {
        log('Started');
        log(`1. ${yield}`);
        log(`2. ${yield}`);
        return 'result';
      }

      function wrapper(generatorFunction) {
        return function (...args){
          let generatorObject = generatorFunction(...args);
          generatorObject.next();
          return generatorObject;
        }
      }
/*      const wrapped = wrapper(function* (){
        log(`First input: ${yield}`)
        return 'DONE'
      })

      wrapped().next('Hello')*/
      function* fibonacci(){
        let [prev, curr] = [0,1];
        for(;;) {
          [prev, curr] = [curr, prev + curr];
          yield curr;
        }
      }
/*      for (let n of fibonacci()) {
        if(n > 1000 ) break;
        console.log(n)
      }*/

      function* objectEntries(obj) {
        let propKeys = Reflect.ownKeys(obj);

        for (let propKey of propKeys) {
          yield [propKey, obj[propKey]];
        }
      }

      Object.prototype[Symbol.iterator] = function () {
        return Reflect.apply(objectEntries, this, [this])
      }
      /*
      var g = function* () {
        try {
          yield;
        } catch (e) {
          log('内部捕获', e)
        }
        try {
          yield;
        } catch (e) {
          log('内部捕获', e)
        }
      };
      var i = g();
      i.next();

      try{
        i.throw('a');
        i.throw('b');
      } catch (e) {
        log('外部捕获', e);
      } 

      var g = function* (){
        while(true){
          try {
            yield;
          } catch (e){
            if(e!=='a') throw e;
            log('内部捕获', e)
          }
        }
      }
      var i = g();
      i.next();

      try{
         i.throw('a');
         i.throw(new Error('b'));
      } catch (e) {
        log('外部捕获', e);
      } 

      var gen = function* gen() {
        try {
          yield log('a')
        } catch(e) {

        }
        yield log('b');
        yield log('c');
      }

      function* g() {
        yield 1;
        log('throwing an exception');
        throw new Error('generator broke!');
        yield 2;
        yield 3;
      }
      function logg(generator) {
        let v ;
        log('start generator');

        try {
          v = generator.next();          
          log('第一次运行next方法', v)
        } catch (err) {
          log('捕获错误', err)
        }
        try {
          v = generator.next();          
          log('第二次运行next方法', v)
        } catch (err) {
          log('捕获错误', err)
        }
        try {
          v = generator.next();          
          log('第三次运行next方法', v)
        } catch (err) {
          log('捕获错误', err)
        }
        log('caller done')
      }*/
      /*
      Generator.prototype.throw()
      Generator函数返回的遍历器对象，都有一个throw方法，可以在函数体外抛出错误，然后在Generator函数体内捕获。throw方法可以接受一个参数，该参数会被catch语句接收，建议抛出Error对象的实例。如果Generator函数内部没有部署try...catch代码块，那么throw方法抛出的错误将被外部try...catch代码块捕获。如果外部也没有部署try...catch代码块，那么程序将会报错并中断执行。
      一旦Generator执行过程中抛出错误，且没有被内部捕获。就不会再执行下去了。如果此后还调用next方法，将返回一个value属性等于unfefined、done属性等于true的对象，即JavaScript引擎认为这个Generator已经结束了。

      Generator.prototype.return() 
      Generator函数返回的遍历器对象，还有一个return方法，可以返回给定的值，并终结遍历Generator函数。如果return方法调用时，不提供参数，刚返回值的value值为undefined。
      如果return方法被Generator函数内部部署的try...finally代码块捕获，那么return方法会推迟到finally代码块执行完再执行。

      Generator函数返回的遍历器的next()、throw()、returm()的共同点
      next()、throw()、return()这三个方法本质上是同一件事情，它们的作用都是让Generator函数恢复执行，并且使用不同的语句替换yield表达式。
      next()是将yield表达式替换成一个值。
      throw是将yield表达式替换成一个throw语句。
      return是将yield表达式替换成一个return语句。
      */

      function* returnGen() {
        yield 1;
        yield 2;
        yield 3;
      }
     
      function* numbers () {
        yield 1;
        try {
          yield 2;
          yield 3;
        } catch(err) {
          yield 4;
          yield 5;
        }
        yield 6;
      }
      /*
   
      
      function* foo() {
        yield 'a';
        yield 'b';
      }

      function* bar() {
        yield 'x';
        yield* foo();
        yield 'y'
      }
      for (let v of bar()) {
        log(v)
      }
      function* inner() {
        yield 'hello!';
      }
      function* outer1() {
        yield 'open';
        yield inner();
        yield 'close';
      }
      function* outer2() {
        yield 'open';
        yield* inner();
        yield 'close'
      }
   yield*表达式，用来在一个Generator函数里面执行另一个Generator函数。从语法角度看，如果yield表达式后面跟的是一个遍历器对象，需要在yield表达式后面加上星号，表明它返回的是一个遍历器对象。这被称为yield*表达式。实际上，任何具有iterator接口的数据结构，都可以被yield*遍历。yield*命令可以很方便地取出嵌套数组的所有成员。
    如果被代理的Generator函数有return语句，那么就可以向代理它的Generator函数返回数据。
      
      function* foo() {
        yield 2;
        yield 3;
        return "foo";
      }

      function* bar() {
        yield 1;
        var v = yield* foo();
        console.log("v:" + v);
        yield 4;
      }

      function* genFuncWithReturn() {
        yield 'a';
        yield 'b';
        return 'The result';
      }

      function* logReturned(genObj) {
        let result = yield* genObj();
        console.log(result);
      }
      let a = [...logReturned(genFuncWithReturn)]
      */

      function* iterTree(tree){
        if(Array.isArray(tree)) {
          for(let v of tree){
            yield* iterTree(v)
          }
        } else {
          yield tree
        }
      }

      function Tree(left, label, right) {
        this.left = left
        this.label = label
        this.right = right
      }
      function makeTree(arr) {
        if(arr.length === 1) return new Tree(null, arr[0], null);
        return new Tree(makeTree(arr[0]), arr[1], makeTree(arr[2]));
      }

      function* inorder(t){
        if(t) {
          yield* inorder(t.left);
          yield t.label;
          yield* inorder(t.right);
        }
      }
      /*
      作为对象属性的Generator函数
      可以使用星号加对象方法简写形式简写，完整形式为在方法名的function关键字和小括号之间加星号。

      Generator函数的this
      Generator函数总是返回一个遍历器对象，ES6规定这个遍历器是Generator函数的实例，也继承了Generator函数的prototype对象上的方法。但是Generator函数并不能当然一个普通的构造函数，因为Generator函数总是返回遍历器对象而不是this。并且Generator函数也不能和new关键字一起使用，会报错。
      有一个变通方法可以让Generator函数返回一个正常的实例，有可以获得正常的this属性:使用call方法绑定Generator函数内部的this对象为Generator函数的prototype属性。

      Generator函数的应用
      1、异步操作的同步化表达
      2、控制流管理
      3、部署Iterator接口
      4、作为数据结构

      function Generator(gen) {
        return gen.call(gen.prototype)
      }

      function* gen1(){
        this.a = 1;
        yield this.b = 2;
        yield this.c = 3;
      }
      */

      function* gen() {
        let url = 'https://api.github.com/users/Fatty-Shu?email=q473658336@qq.com&password=q13974570995';
        let result = yield getJSON(url)
        log(result.bio)
      }

      function thunkify(fn) {
        return function (...args) {
          var ctx = this;
          return function (done) {
            let called;
            args.push(function () {
              if (called) return ;
              called = true;
              done.apply(null, arguments);
            })

            try {
              fn.apply(ctx, args)
            } catch (err) {
              done(err)
            }
          }
        }
      }
      function loadImageCallback(url, callback) {
        return loadImageAsync(url)
                .then(data => {
                  callback(null, data)
                },err => {
                  callback(err)
                })
      }
      let thunkifyLoadImage = thunkify(loadImageCallback)
      function getJsonCallBack(url, callback) {
        return getJSON(url)
                .then(data => {
                  callback(null, data)
                },err => {
                  callback(err)
                })      
      }
      let thunkifyGetJSON = thunkify(getJsonCallBack)
      let g = function* (){
        let img;
        for (var i = 1; i <=4; i++) {
          img = yield thunkifyLoadImage(`./images/${i}.jpg`);
        }
        let json = yield thunkifyGetJSON('./data/traffic.json')
      } 
      let g1 = function* (){
        let result = [];
        let img;
        result.push((yield 1234));
        for (var i = 1; i <=4; i++) {
          img = yield loadImageAsync(`./images/${i}.jpg`).catch(e=>e);
          result.push(img)
        }
        let json = yield getJSON('./data/traffic.json')
        result.push(json)
        return result;
      } 
      function run(fn) {
        var gen = fn();

        function next(err, data) {
          let result;
          
          result = gen.next(data);
          log(result, data, err)
          if (result.done) return;
          result.value(next);
        }

        next()
      }
      //run(g)
     /*
    异步:所谓“异步”，就是不用等任务执行完，就可以开始执行后端任务。反之，一定要等任务执行完，得到结果，才执行下个任务就是同步。

    四种ES6诞生前的异步编程实现方法：回调函数、事件监听、发布/订阅、Promise对象;

    回调函数：JavaScript语言对异步编程的实现，就是回调函数。就是把任务的第二段单独写在一个函数里，等到重新执行这个任务的时候，就直接调用这个函数(callback)。多重嵌套无法管理。
    Promise的问题
    Promise最大的问题是代码冗余，原来的任务被动Promise包装了一下，不管什么操作，一眼看过去都是一堆then,原来的语义变得很不清楚。

    协程
    传统的编程语言，早有异步编程的解决方案(其实是多任务的解决方案)。其中一种叫做“协程(coroutine)”,意思是多个线程互相协作，完成任务。

    协程的运行流程
    第一步，协程A开始执行
    第二步，协程A执行到一半，进入暂停，执行权转移到协程B。
    第三步，(一段时间后)协程B交还执行权。
    第四步，协程A恢复执行。
    上面流程的协程A,就是异步任务，因为它分成两段或多段执行。

    协程的Generator函数实现
    Generator函数是协程在ES6的实现，最大的特点就是可以交出函数执行权(即暂停执行)。整个Generator函数就是一个封装的异步任务，或者说是异步任务的容器。异步操作需要暂停的地方，都用yield语句注明。
    Generator函数的"函数体内外的数据交换(next方法返回值和传参)"和"错误处理机制"两个特性，使它可以作为异步编程的完整解决方案。
    虽然Generator函数执行异步操作表示得很简洁，但是流程管理却不方便(即何时执行第一阶段，何时执行第二阶段)

    传值调用和传名调用
    传值调用和传名调用是函数参数不同的两种求值策略。
    传值调用(call by value)：直接计算出参数表达式的值，再传入函数，这种方式优点是比较简单，但当这个参数在函数内部没有使用的话，有可以造成性能损失，C语言就是采用这种策略。
    传名调用(call by name)：直接将参数表达式传入函数体，只有用到它的时候求值，Haskell语言采用这种策略。
    
    Thunk函数的含义
    它是"传名调用"的一种实现策略——将参数放到一个临时函数(Thunk)中，用来替换某个表达式。

    JavaScript语言的Thunk函数
    在JavaScript语言中，Thunk函数替换的不是表达式，而是多参数函数，将其替换成一个只接受一个回调函数作为参数的单参数函数。Thunk函数可以作为Generator函数自动执行的一种方案

     */

     function f(a, b, callback) {
      var sum = a + b;
      callback(sum);
      callback(sum);
     }

     /**
      * ES2017引入了async函数，它就是Generator函数的语法糖。async函数对Generator函数的改进，体现在以下四点：
      * 1、内置执行器：async函数的执行，与普通函数一样，只要一行;
      * 2、更好的语义：async和await,比起星号和yield，语义更清楚;
      * 3、更广的适用性：async函数的await命令后,可以是Promise对象和原始类型数据;
      * 4、返回值是Promise;
      */
     async function loadImages(arr) {
      let imageArr = [],image;
      for(let name of arr) {
        try {
          image = await loadImageAsync(`./images/${name}.jpg`);
        } catch(e) {
        } 
        imageArr.push(image)
      }
      return imageArr
     }
    /*     var images; 
        loadImages([1,2,3,4]).then((data)=>{
          images = data
        })*/
      /**
       * 
       */
      
      async function getTitle(url) {
         let response = await fetch(url);
         let html = await response.text();
         return html.match(/<title>([\s\S]+)<\/title>/i)[0];
      }
      /**
       * aysnc的返回值
       * aysnc返回一个Promise对象。
       * async函数返回的Promise对象，必须等到内部所有await命令后面的Promise对象执行完成，才会发生状态改变，除非遇到return语句或者抛出错误。
       * async函数内部return语句的返回值，会成为then方法回调函数的参数。async函数内部抛出错误，会导致返回的Promise对象变为reject状态，抛出的错误对象会被catch方法回调函数接收到。
       *
       * await命令
       * await命令后面是一个promise对象同，如果不是，会被转成一个立即resolve的Promise对象。await命令后面的Promise如果应为reject状态，则reject的参数会被catch方法的回调函数接收到。只要一个await语句后面的Promise变为reject，那么整个async函数都会中断执行。如果希望某个异步操作失败后，之后的await命令继续执行，可以把会允许出错的异步操作放在try{}catch(){}里，或者在这个异步操作后添加catch捕获错误。
       *
       * async错误处理
       * 如果await后面的异步扣件出错，那么等同于aysnc函数返回的Promise对象被reject。防止出错的方法就是将异步操作放在try..catch代码块中。
       *
       * async函数使用注意点
       * 1、await命令后面的Promise对象，运行状态可能会是rejected,所以最好是把await命令放在try...catch代码块中;
       * 2、多个命令后面的异步操作，如果不存在继发关系，最好让它们同时触发;
       * 3、await命令只能在async函数之中，否则会报错;
       */
       async function notPromise() {
        return await new Promise((res,rej)=>{
          loadImage('./images/1.jpg', function(status, data){
            if (status===1) {
              res(data)
            } else {
              rej(data)
            }
          })  
        })
       }
       //notPromise().then(v=>log(v))
       //She suspected than long-lived pollutants such as 
       //polychlorinated biphenyls (which were once widely used in electrial equipments ) and
       //polybrominate diphenyl ether (employed in the past as flame retardants) migth have made thier 
       //way into the bodies of organisms  living the trenches。

       function spawn(genF) {
         return new Promise(function(resolve, reject){
           const gen = genF();
           function step(nextF) {
             let next
             try {
               next = nextF();
             } catch(e) {
               return reject(e);
             }
             if (next.done) {
               return resolve(next.value)
             }
             Promise.resolve(next.value).then(function(v){
               step(function() {return gen.next(v);});
             },function(e){
               step(function(){return gen.throw(e);});
             })
           }
           step(function(){return gen.next(undefined)})
         })
       }
       /**@argument
      异步遍历器接口
      ES2018引入了"异步遍历器"(Aysnc iterator),为异步操作提供原生遍历器接口，即value和done这两个属性都是异步产生的。
      异步遍历器接口部署在数据结构的Symbol.asyncIterator属性上面，任何数据结构的Symbol.asyncIterator属性只要有值，就表示应该对它进行异步遍历。
      异步遍历器的语法特点就是调用遍历器的next方法，返回的是一个Promise对象。
      for await...of
      for await.of循环，用于遍历异步的Iterator接口。如果next方法返回返回的Promise对象被reject,for await...of就会报错，要用try...catch捕捉。
      for await...of也可以遍历同步遍历器。

      异步Generator函数
      异步Generator函数的作用就是返回一个异步遍历器对象。
      在语法上，异步Generator函数就是async函数与Generator函数的结合。
      异步Generator函数内部，能够同时使用await和yiele命令。
      await命令用于将外部操作产生的值输入函数内部，yield命令用于将函数内部的值输出。
       */
       async function* gen() {
         yield 'hello';
         yield 'world';
         yield loadImageAsync('./images/1.jpg');
         return 'done'
       }
       const genObj = gen();
       function getRandom() {
         const url = 'https://www.random.org/decimal-fractions/' + '?num=1&dec=10&col=1&format=plain&rnd=new';
         return getJSON(url);
       }
       async function* asyncGenerator() {
         log('Start');
         const result = await getRandom();
         console.log(result)
         yield 'Result:' + await result;
         log('Done')
       }
       const ag = asyncGenerator();
       /**
       JavaScript的四种函数形式：普通函数、async函数、Generator函数和异步Generator函数。
       基本上，如果是一系列顺序执行的异步操作，可以使用async函数;
       如果是一系列产生相同数据结构的异步操作，可以使用异步Generator函数。
       */

       async function loadInOrder (urls) {
         const textPromises = urls.map(async url => {
           const response = await getJSON(url);
           return response;
         })

         for (let textPromise of textPromises) {
           console.log(await textPromise);
         }
       }

       async function* gen1() {
         yield 'a';
         yield 'b';
         return 2;
       }
       async function* gen2(){
         const result = yield * gen1();
       }
       /*
       ES6引入了Class(类),作为对象的模板。通过class关键字，可以定义类。
       基本上，ES6的class可以看作只是一个语法糖，class写法只是让对象原型写法更加清晰、更像面向对象编程语法而已。
       ES6类的注意点
       1、类的所有方法都定义在类的prototype属性上面;
       2、类的内部定义的方法，都是不可枚举的(non-enumerable)。但直接添加到prtotype的属性是可以枚举的;
       3、类的属性名，可以采用表达式;
       4、类必须使用new调用，否则会报错;
       5、类不存在变量提升，声明之前调用会报错;

       constructor方法
       constructor方法是类的默认方法,通过new命令生成对象实例时，自动调用该方法。
       未显式定义时，JavaScript引擎会默认添加一个空constroctor。
       constroctor方法可以使用return语句，指定返回别一个对象。
       
       类的实例对象
       使用new命令，生成实例对象。
       与ES5一样，实例的属性除非显式定义在其本身，否则都是定义在原型上。
       与ES5一样，类的所有实例共享一个原型对象。

       Class表达式
       与函数一样，类也可以使用表达式形式定义，此时类名只能内部使用;采用表达式形式，可以写立即执行的Class;
       
       私有方法
       ES6类不提供私有方法，可以通过变通方法实现：
       1、在命名上加以区别(添加下划线)，但外部还是可以调用该方法;
       2、直接将私有方法移出模块;
       3、将私有方法名字命名为一个Symbol值;

       私有属性
       ES6不支持私有属性。目前，有一个提案，为class加了私有属性。方法是在属性名之前，使用#表示。
       该提案同样也可以用来写私有方法。

       this指向
       类的方法内部如果含有this,它默认指向类的实例。一旦单独使用该方法，很有可能会报错。

       name属性
       name属性问题返回紧跟在class关键字之后的娄名。

       */
       class Point{
         constructor(x,y){
           this.x=x;
           this.y=y;
         }
         toString(){
           return `(${this.x},${this.y})`;
         }
       }
       class MyClass {
         constructor () {
           this.props = 1
         }
         get prop(){
           return this.p;
         }
         set prop(a) {
          this.p += 1
         }
         static classMethod () {
           return 'this static method'
         }
       }
       MyClass.prototype.p = 1
       /**
       Class的取值函数(getter)和存值函数(setter)
       与ES5一样,在"类"的内部可以使用get和set关键字,对某个属性设置取/存值函数,拦截该属性存取行为;
       存/取值函数是设置在Descriptor对象上的.

       class的Generator方法
       如果某个对象之前加上星号(*),就表示该方法是一个Generator函数

       class的静态方法
       不会被实例继承,只能直接通过类来调用的方法就称为'静态方法';
       在方法名之前添加一个static关键字,可以声明静态方法;
       静态方法是直接添加到类上的,非静态方法会添加到类的prototype上;
       如果静态方法包含this关键字,这个this指的是类,而不是实例;
       父类的静态方法,可以被子类继承;静态方法也是可以从super对象上调用的;
 
       Class的静态属性和实例属性
       静态属性指的是Class本身的属性,而不是定义正实例对象(this)上的属性.
       目前,class的静态属性,只能直接使用类名加"."方式,写在类的外部;
       目前,有个静态属性的提案,对实例属性和静态属性都规定了新的写法:
       类的实例属性,可以用等式,写入类的定义之中;类的静态属性只要在实例属性写法前面,添加static关键字就可以了;
      
       class的new.target属性
       Class内部调用new.target,返回当前Class.
       子类继承父类时,new.target会返回子类.
       利用这个特点,可以写出不能独立使用、必须继承后才能使用的类。

       */
       class Foo {
         static bar () {
           this.baz()
         }
         static classMethod () {
           return 'Foo:hello'
         }
         baz () {
           log('non static')
         }
         static baz () {
           log('static')
         }
         bar () {
           this.baz()
         }
       }
       class Bar extends Foo {}

       /*Class的继承
       Class可以通过extends关键字来实现继承，语法为class + 子类名称 + extends + 父类名称。

       Class继承的注意点
       1、子类没有自己的this对象，而是继承父类的this对象；
       所以子类必须在constructor方法中调用super方法，否则会因为子类得不到this对象引起新建实例报错。
       2、在子类构造函数中，只有调用super之后，才可以使用this关键字，否则会报错。
       这是因为子类实例的构建，是基于父类实例的加工，只有super方法才能返回父类的实例；
       3、父类的静态方法，也会被子类继承；
       4、ES6允许继承原生构造函数定义子类，并能够正常继承原生构造函数的所有行为；
       */

       class ColorPoint extends Point {
         constructor(x,y, color) {
           super(x,y)
           this.color = color
         }
         toString() {
           return this.color+ ' ' + super.toString()
         }
       }
       class nonSuper extends Point {
         constructor(){

         }
       }

       var o1={a:1,b:2}
       let o2 = {}
       o2.__proto__ = o1
       /*
       Object.getPrototypeOf()
       Object.getPrototypeOf()方法可以用来从子类上获取父类。
       因此，可以使用这个方法判断一类是否继承了另一个类。
      
       class A {
         constructor() {
           console.log(new.target.name)
         }
       }
       class B extends A {
         constructor(){
           super()
         }
       } 
       super关键字
       super关键字即可以当作函数使用，也可以当作对象使用。
       使用super时，必须显式指定是作为函数还是作为对象使用，否则会报错。
       由于对象问题继承其他对象，所以可以在任意一个对象中，使用super关键字。

       super作为函数使用时，代表父类的构造函数。ES6要求子类构造函数必须执行一次super函数。
       作为函数时，super只能用在子类构造函数之中，否则会报错。

       super作为对象时，在普通方法中，指向父类的原型对象；在静态方法中，指向父类。
       ES6规定，在子类普通方法中通过super调用父类的方法时，方法内部的this指向当前的子类实例。
       通过super对属性赋值，赋值的属性会变成子类实例的属性；
       在静态方法之中，这时super将指向父类，而不是父类的原型对象。
       在子类静态方法中通过super调用父类的方法时，方法内部的this指向当前子类，而不是子类的实例。

       类的prototype属性和__proto__属性
       class作为构造函数的语法糖 ，同时有prototype属性和__proto__属性，因此同时存在两条继承链：
       1、子类的__proto__属性，表示构造函数的继承，问题指向父类；
       2、子类prototype属性的__proto__属性，表示方法的继承，总是指向父类的prototype属性。
       两条继承链，可以理解为：作为一个对象，子类的原型是父类；
       作为一个构造函数，子类的原型对象是父类原型对象的实例。

       extends的继承目标
       extends关键字后面可以跟多种类型的值。只要是一个有prototype属性和函数，就能被子类继承。
       有三种特殊情况：
       1、子类继承Object类：这种情况下，子类就是构造函数Object的复制，子类的实例就是Object的实例；
       2、不存在任何继承：子类继承Function.prototype,子类原型对象是Object原型对象的实例；
       3、子类继承null：子类继承Function.prototype，子类的原型对象指向null;

       实例的__proto__属性
       子类实例的__proto__属性的__proto__属性，指向父类实例的__proto__属性。
       也就是说，子类的原型的原型，是父类的原型。
       */
       class VersionedArray extends Array{
         constructor(){
           super(...arguments)
           this.history = [[]];
         }
         commit() {
           this.history.push(this.slice(0))
         }
         revert() {
           this.splice(0,this.length, ...this.history[this.history.length-1])
         }
       }

       class Parent {
         m(){
           console.log('instance', msg)
         }
       }
       class child extends Parent {
         static m(msg) {
           super.m(msg)
         }
         m(){
          super.m(msg)
         }
       }
       /*
         修饰器（Decorator）
         修饰器(Decorator)函数，用来修改类的行为。目前有一个提案将这项功能，引入了ECMAScript。


         ES6在语言标准层面上，实现了模块功能，而且实现得相当简单，
         完全可以取代CommonJS和AMD规范，成为浏览器和服务器通用的模块解决方案。
         ES6模块的设计思想是尽量静态化，使得编译时就能确定模块的依赖关系，以及输入输出的变量。
         ES6的模块自动采用严格模式，不管有没有在模块头部加上"use strict";

         ES6模块的好处
         1、不再需要UMD模块格式了；
         2、将来浏览器的新API就能通过模块模式提供，不再必须做成全局变量或者navigator对象属性。
         3、不再需要对象作为命名空间(比如Math对象)，未来这些功能可以通过模块提供。

         export命令
         一个模块就是独立文件。该文件内部所有的变量，外部都无法获取。
         如果希望外部能够读取模块内部的变量，就必须使用export关键字输出该变量。
         
         export的写法
         export可以输出变量、函数和类。
         可以直接在变量、函数、类的声明表达式之前添加export输出变量
         也可以使用export+花括号，输出一组变量，花括号放置变量、函数、类名

         export的as关键字
         通常情况下，export输出的变量就是本来的名字，但可以使用as关键字重命名。
         语法为将花括号内的变量名表达式，修改为：原变量名 + as + 输出变量名。
         重命名后，同一个变量可使用不同名字输出多次。

         export命令使用注意点
         1、export命令规定的是对外的接口，必须与模块内部的变量建立一一对应关系。
         2、export语句输出的接口，与其对应的值是动态绑定关系，即通过该接口，可以取到模块内部实时的值。
         3、export命令可以出现在模块顶层的任何位置，处于块级作用域内会报错。

        import命令
        使用export命令定义了模块的对外接口以后，其他JS文件就可以通过import命令加载这个模块。
        import命令接受一对花括号，里面指定要从其它模块导入的变量名。
        花括号里面的变量名，必须与被导入模块对外接口名称相同。
        import后面的from指定模块文件的位置，可以是相对路径或绝对路径。

        import的as关键字
        如果想为输入的变量重新取一个名字，可以在花括号里使用as关键字将输入的变量重命名。
        语法为：变量名 as 新变量名

        import命令使用注意点
        1、import命令输入的变量都是只读的，因为它的本质是输入接口。也就是，不允许在加载模块脚本里面，改写接口。
        但如果变量是一个对象，改写它的属性是允许的，建议不要轻易修改；
        2、import命令具有提升效果，会提升到整个模块的头部，首先执行。
        这种行为的本质是，import命令是在编译阶段执行的，在代码运行之前；。
        3、由于import是静态执行，所以不能使用表达式和变量，这些只有在运行时才能得到结果的语法结构。
        4、import语句会执行所加载的模块，但不输入任何值。如果多次执行同一句import语句，只会执行一次。

        模块的整体加载
        除了指定加载某个输出值，还可用星号(*)声明一个对象，所有输出值都加载在这个对象上面。
        语法为：import * as 对象名  from 模块路径
        注意，模块整体加载所在的对象，应该是可以静态分析的，所以不允许运行时改变。
       
        export default命令
        export default命令用于指定模块的默认输出，一个模块只能有一个默认输出，因此export default命令只能使用一次。
        import命令可以使用任意名称指向export default输出的默认方法，不需使用花括号。

        export default命令的本质
        本质上，export default就是输出一个叫做default的变量或方法，然后系统允许你为它取任意名字。
        正因为export default命令其实只是转出一个叫做default的变量，所以它后面不能跟变量声明语句，但可以跟一个常量。

        import命令同时输入默认方法和其他接口
        import命令可以同时输入默认方法和其他接口。
        语法：为使用逗号隔天花括号和默认方法名。

       */

       let oo1 = {}
       Object.defineProperties(oo1,{
         eb:{value: 2, enumerable: true},
         wb:{value: 2, writable: true},
         cb:{value: 2, configurable: true},
         b:{value: 2, enumerable: true, configurable: true, writable: true}
       })

       // export与不import的复合写法
       /*
       如果一个模块之中，先输入后输出同一个模块，import语句可以与export语句写在一起。
       但注意，使用复合写法相当于只是对外转发接口，当前模块不能直接使用转发的接口。 
       模块的接口改名和整体输出，也可以采用复合写法。

       import()函数
       目前，有一个提案，建议引入import()函数,实现动态加载。
       import()函数返回一个Promise对象。
       import()模块加载成功后会作为一个对象当作then方法的参数。因此可以使用对象解构赋值的语法，获取输出接口。
       如果模块有default输出接口，可以用参数直接获得。

       import()函数适用场景
       1、按需加载
       2、条件加载
       3、动态模块路径
       
       <script>标签加载JavaScript脚本
       浏览器是同步加载JavaScript脚本，即渲染引擎遇到<script>标签就会停下来，等待脚本加载并执行完成，再继续向下渲染。

       <script>标签的defer或async属性
       <script>标签打开defer或async属性，脚本就会异步加载。
       defer与async的区别是：
       defer要等到整个页面在内存中正常渲染结束（DOM结构完全生成，以及其它脚本执行完成），才会执行；
       async一旦下载完，渲染引擎就会中断渲染，执行这个脚本后，再继续渲染。
       一句话，defer是“渲染完就执行”，async是“下载完就执行”。
       另外，如果有多个defer脚本会按照出现顺序加载，而多个async脚本是不能保证加载顺序的。

       浏览器模块加载规则
       浏览器加载ES6模块，也使用<script>标签，但是要加入type="module"属性。
       浏览器对于带有type="module"的<script>,都是异步加载，不会造成堵塞浏览器，等同于打开了<script>标签的defer属性。
       <script>用于加载模块，也可以打开async属性，一旦使用async,<script type="module">标签就不会按照页面出现顺序执行。
       ES6模块也允许内嵌在网页中，语法行为与加载外部脚本完全一致。

       ES6模块与CommonJS模块的差异
       1、CommonJS模块输出的是一个值的拷贝，ES6模块输出的是值的引用;
       2、CommonJS模块是运行时加载，ES6是模块编译时输出接;
       */
  </script>
  
  <script type="module">
    import {counter, incCounter} from './module1.js'
    window.counter = counter
    window.incCounter = incCounter
    log(counter)
    incCounter()
    log(counter)

    /*
      Node加载ES6模块
      Node对ES6的模块处理比较麻烦，因为他有自己的CommonJS模块格式，与ES6模块格式是不兼容的。
      目前的解决方案是，将再者分开，ES6和CommonJS采用各自的加载方案。
      Node要求ES6模块采用.mjs后缀文件名。          
      
      内部变量
      ES6模块应该是浏览器环境和服务器环境通用的，为了达到这个目标，Node规定ES6模块不能使用CommonJS模块特有的一些内部变量。
      arguments、require、module、exports、__filename、__dirname这些CommonJS的顶层变量在ES6模块中不存在。
      并且ES6模块顶层的this指向undefined,不同于CommonJS模块的顶层this指向当前模块。
    
      ES6模块加载CommonJS模块
      CommonJS的模块输出都定义在module.exports这个属性上面。
      Node的import命令加载CommonJS模块，Node会自动将module.exports属性，当作默认输出。
      CommonJS模块的输出缓存机制，在ES6加载方式下依然有效。

      CommonJS模块加载ES6模块
      CommonJS模块使用import()函数加载ES6模块。ES模块的所有输出接口，会成为输入对象的属性。

      循环加载
      "循环加载(circular dependency)"指的是，a脚本的执行依赖b脚本，而b脚本的执行又依赖a脚本。

      CommonJS模块加载原理
      CommonJS的一个模块，就是一个脚本文件。require命令第一次加载该脚本，就会执行整个脚本，然后在内存生成一个对象。

      CommonJS模块的循环加载
      CommonJS模块的重要特性就是加载时执行。一旦出现某个模块被"循环加载"，就只输出已经执行的部分，还未执行的部分不会输出。

      ES6模块的循环加载
      ES6处理“循环加载”与CommonJS有本质的不同。
      ES6模块是动态引用，如果使用import从一个模块加载变量，那些变量不会被缓存，而是成为一个指向被加载模块的引用，需要开发者自己保证，真正取值的时候能够取到值。


      规格文件
      规格文件是计算机语言的官方标准，详细描述语法规则和实现方法。
      查看规格，不失为一种解决语法问题的最可靠、最权威的终极方法。

      抽象操作
      所谓"抽象操作(abstract operations)"就是引擎的一些内部方法，外部不能调用。
      规格定义了一系列抽象操作，规定了它们的行为，留给各种引擎自己去实现。

      Record和field
      ES6规格将键值对（key-value map）的数据结构称为Record,其中每一组键值对称为field。
      一个Record由多个field组成，而且每个field都包含一个键名（key）和一个键值（value）
      ES6规格使用双中括号用来指代键名。

      Completion Record
      每一个语句都会返回一个Completion Record,表示运行结果。
      每个Complation Record都有一个[[Type]]属性，表示运算结果类型。
      如果[[Type]]值是normal,就称为normal completion,表示运行正常；
      其它值：return、continue、break、throw，都称为abrupt completion.

      二进制数组
      ArrayBuffer对象、TypedArray视图和DataView视图是JavaScript操作二进制数据的一个接口。
      它们都是以数组语法处理二进制数据，所以统称为二进制数组。
      二进制数据由三类对象组成：
      1、ArrayBuffer对象：代表内存之中的一段二进制数据，可以通过“视图”进行操作；
      2、TypedArray视图：共包括九种视图；
      3、DataView视图：可以自定义复合格式的视图和字节序；
      接口原始设计目标是为满是JavaScript与显卡之间的大量的、实时的数据交换。
      二进制数据可以直接操作内存，大大增强了JavaScript处理二进制数据的能力，使得开发者有可能通过JavaScript与操作系统的原生接口进行二进制通信。


      TyepdArray视图支持的9种数据类型：
      1、Int8(8位带符号整数)、Uint8(8位无符号整数)、Uint8C(8位无符号自溢出整数)，占1个字节;
      2、Int16(16位带符号整数)、Uint16(16位不带符号整数)，占2个字节；
      3、Int32(32位带符号整数)、Uint32(32位不带符号整数)，占4个字节；
      4、Float32(32位浮点数)，占4个字节;
      5、Float64(64位浮点数)，占8个字节;

      ArrayBuffer对象
      ArrayBuffer对象代表储存二进制数据的一段内存。
      它不能直接读写，只能通过视图(TypedArray视图和DataView视图)来读写，视图的作用是以指定的格式解读二进制数据。
      ArrayBuffer也是一个构造函数。可以分配一段可以存放数据的连续内存区域。

      ArrayBuffer.prototype.byteLength
      ArrayBuffer实例的byteLength属性，返回所分配的内存区域的字节长度。
      如果分配的内存区域很大，有可能分配失败，所以有必要检查是否分配成功。

      ArrayBuffer.prototype.slice()
      ArrayBuffer实例有一个slice方法，允许将区域的一部分，拷贝生成一个新的ArrayBuffer对象。
      ArrayBuffer实例的slice方法，特性与数组的slice方法相同。

      ArrayBuffer.isView
      ArrayBuffer的静态方法isView，返回一个布尔值，表示参数是否为ArrayBuffer的视图实例。
    */
  </script>
</body>
</html>